﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Platform.Infrastructure;
using Platform.ObjectModel.Collections;
using Platform.ObjectModel.Rules;
//using ProtoBuf.Serializers;

namespace Platform.ObjectModel.Design
{
	/// <summary>
	/// Representa a estrutura de um documento
	/// </summary>
	public class Schema : DocumentFlake//, IQualifiedDataObject //DataObjectFlake, IQualifiedDataObject
	{
		//IFormatter;
		public static readonly DataObjectName SchemaNamespace = new DataObjectName( "Schema", "Plaform.ObjectModel" );


		/// <summary>
		/// Inicializa um novo schema
		/// </summary>
		/// <param name="name"></param>
		public Schema( DataObjectName name )
			: base( SchemaNamespace )
		{
			Arguments.IsNotNull( "name", name );

			this.QualifiedName = name;
			this.Fields = new FieldCollection( this );
			this.Rules = new SchemaRuleCollection();
			this.Relations = new FieldCollection( this );
			//this.Relations = new FieldRefCollection();
			// TODO: na próxima versão o usuário que define o novo esquema poderá escolher qual campo é a chave
			this.FieldKey = new DataObjectName( "Token", name.FullName );
		}


		/// <summary>
		/// Obtém ou define o nome que é exibido para o usuário do sistema
		/// </summary>
		public string FriendlyName
		{ get; set; }

		#region IQualifiedDataObject Members

		/// <summary>
		/// Obtém o nome que identifica o schema
		/// </summary>
		public DataObjectName QualifiedName
		{
			get;
			private set;
		}

		#endregion

		/// <summary>
		/// Obtém a coleção de campos do schema
		/// </summary>
		public FieldCollection Fields
		{ get; private set; }

		/// <summary>
		/// 
		/// </summary>
		public FieldCollection Relations
		{ get; private set; }

		/// <summary>
		/// Obtém as regras do schema
		/// </summary>
		public SchemaRuleCollection Rules
		{ get; private set; }

		/// <summary>
		/// Obtém ou define o nome do campo que é utilizado para identificar unicamente
		/// o documento
		/// </summary>
		/// <remarks>
		/// Pode ser definido qualquer campos do <see cref="schema"/> 
		/// </remarks>
		public DataObjectName FieldKey
		{ get; internal set; } 

		/// <summary>
		/// Realiza a criação do schema do schema
		/// </summary>
		/// <returns></returns>
		internal static Schema CreateSchemaForDocumentSchema()
		{
			SchemaBuilder<Schema> builder = new SchemaBuilder<Schema>( new Schema( SchemaNamespace ) );

			builder.Field( s => s.Token ).FriendlyName( "Token" );
			builder.Field( s => s.FriendlyName ).FriendlyName( "Schema" ).WithRules( new Required() );
			builder.Field( s => s.SchemaName ).FriendlyName( "Layout utilizado por este documento" );
			builder.Field( s => s.QualifiedName ).FriendlyName( "Identiticador único" );
			builder.RelateWith<FieldDescriptor>( s => s.Fields ).FriendlyName( "Campos" );
			builder.RelateWith<RuleBase>( s => s.Rules ).FriendlyName( "Regras" );

			return builder.Schema;
		}

		#region [ class FieldCollection ]

		public sealed class FieldCollection : HybridCollection<DataObjectName, FieldDescriptor>
		{
			internal FieldCollection( Schema schema )
				: base( field => field.QualifiedName )
			{
				this.Schema = schema;
			}


			public FieldDescriptor this[ string fieldName ]
			{
				get { return this.Find( fieldName ); }
			}

			/// <summary>
			/// 
			/// </summary>
			public Schema Schema
			{ get; private set; }


			/// <summary>
			/// Procura pelo campo que melhor se encaixa param <paramref name="fieldName"/>
			/// </summary>
			/// <param name="fieldName"></param>
			/// <returns></returns>
			private FieldDescriptor Find( string fieldName )
			{
				Arguments.DontHaveInvalidChars( "fieldName", fieldName, '[', ']' );

				foreach ( var field in this )
				{
					// Se achou pelo nome mais completo, então não vai encontrar outro campo e esse mesmo
					if ( field.QualifiedName.FullName.Equals( fieldName ) )
					{
						return field;
					}
					// Faz o match somente pelo nome, ignorando o namespace, somente para os casos
					// onde o namespace é igual ao fullname do schema
					else if ( this.Schema.QualifiedName.FullName == field.QualifiedName.Namespace
						&& field.QualifiedName.Name.Equals( fieldName ) )
					{
						return field;
					}
				}

				return null;
			}
		}

		#endregion

		//#region [ class FieldRefCollection ]

		/// <summary>
		/// Coleção de relacionamentos com outros documentos
		/// </summary>
		//public sealed class FieldRefCollection : HybridCollection<DataObjectName, FieldRefDescriptor>
		//{

		//	public FieldRefCollection()
		//		: base( field => field.QualifiedName )
		//	{ }
		//}

		//#endregion

		#region [ class SchemaRuleCollection ]

		public sealed class SchemaRuleCollection : CollectionBase<RuleBase>
		{
			public SchemaRuleCollection()
				: base()
			{ }
		}

		#endregion
	}
}

